<?php
/**
* Handle all page-related administration activity.
*
* Copyright (c) 2009 James Gauld <james@jamesgauld.net>
* This file is part of Scribble.
* @license http://www.scribblecms.co.uk/license.txt
*
* @package Scribble
*/
use Buan\Core;
use Buan\Config;
use Buan\ExtensionManager;
use Buan\Inflector;
use Buan\Model;
use Buan\ModelCollection;
use Buan\ModelCriteria;
use Buan\ModelManager;
use Buan\ModelRelation;
use Buan\SystemLog;
use Buan\UrlCommand;
class PageController extends ScribbleAdminController {

	/**
	* List all comments.
	*
	* @param array
	* @return ScribbleAdminView
	*/
	public function comments($params) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}

		// Prepare main view
		$view = new ScribbleAdminView();
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/comments.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/comments.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Gather filters
		$filter = new StdClass();
		$filter->isvisible = !isset($_GET['s']) || $_GET['s']==='' ? NULL : (int)$_GET['s'];
		$filter->page_id = isset($_GET['pageid']) && $_GET['pageid']!='' ? (int)$_GET['pageid'] : NULL;
		$filter->q = isset($_GET['q']) && $_GET['q']!='' ? $_GET['q'] : NULL;
		$filter->df = isset($_GET['df']) && $_GET['df']!='' ? $_GET['df'] : NULL;
		$filter->dt = isset($_GET['dt']) && $_GET['dt']!='' ? $_GET['dt'] : NULL;
		$view->filter = $sView->filter = $filter;

		// Prepare pager
		$pager = new StdClass();

		// Build Query
		$c = new ModelCriteria();
		$c->addOrder('datecreated', 'DESC');

		// Filters: Status, Page ID
		if($filter->isvisible!==NULL) {
			$c->addClause(ModelCriteria::EQUALS, 'isvisible', $filter->isvisible);
		}
		if($filter->page_id!==NULL) {
			$c->addClause(ModelCriteria::EQUALS, 'page_id', $filter->page_id);
		}

		// Filter: Keywords
		if($filter->q!==NULL) {
			$words = explode(" ", $filter->q);
			foreach($words as $w) {
				$c->addClause(ModelCriteria::LIKE, 'body', "%{$w}%");
			}
		}

		// Filter: Date range
		if($filter->df!==NULL) {
			list($d,$m,$y) = explode("/", $filter->df);
			$c->addClause(ModelCriteria::GREATER_THAN, 'datecreated', mktime(0, 0, 0, $m, $d, $y));
		}
		if($filter->dt!==NULL) {
			list($d,$m,$y) = explode("/", $filter->dt);
			$c->addClause(ModelCriteria::LESS_THAN, 'datecreated', mktime(23, 59, 59, $m, $d, $y));
		}

		// Execute query, load Comments
		$comments = ModelManager::select('PageComment', $c);
		$view->comments = $comments;

		// Result
		return $view;
	}

	/**
	* Create/edit a page.
	*
	* A new version of a Page is created everytime the page is edited.
	*
	* @param array Action parameters
	* @param string Page ID
	* @return View
	*/
	private function edit($params, $pageId) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}

		// Load page
		$page = Model::create('Page');
		if($pageId!='create') {
			$page->id = $pageId;
			if(!$page->getModelManager()->load($page)) {
				SystemLog::add('Cannot find matching page.', SystemLog::WARNING);
				return new ScribbleAdminView();
			}
			$version = $page->findVersion();
		}
		else {
			// Ensure this new page belongs to the current Site and has an initial
			// version
			$page->addRelatives(SiteManager::getCurrentSite());
			$version = Model::create('PageVersion');
			$version->targetmenus = array(current(Config::get('ext.Scribble.menuSlots')));
			$page->addRelatives($version, 'master');
		}
		//$view->page = $page;
		//$view->version = $version;

		// Auth: Can the user create/modify this page
		if(!ScribbleAuth::authorize(ScribbleSession::getUser(), 'page-author', $page->getPathToSite())) {
			SystemLog::add('Sorry, you do not have the authority to create or modify this page.', SystemLog::WARNING);
			return $view;
		}

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/edit.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');
		//$sView->page = $page;
		//$sView->version = $version;

		// If a template hasn't yet been chosen for the Page, or we're re-choosing a
		// different template, then present the user with a list of available
		// templates to choose from
		if(isset($_GET['tchange']) || (!isset($_GET['t']) && !isset($_POST['template']) && !$version->isInDatabase())) {
			return $this->editSelectTemplate($version);
		}

		// Determine which template will be used, either via the GET/POST data (for
		// new pages), or via the latest content revisions.
		if(isset($_POST['template']) || isset($_GET['t'])) {
			list($outerTemplate,$innerTemplate) = explode(",", isset($_POST['template']) ? $_POST['template'] : $_GET['t']);
		}
		else if($version->isInDatabase()) {
			$outerTemplate = $version->getOuterContent()->template;
			$innerTemplate = $version->getInnerContent()->template;
		}

		// Load instances of each template's class
		try {
			$outerTemplate = $view->outerTemplate = ScribbleTemplateManager::getTemplateByName($outerTemplate);
			$innerTemplate = $view->innerTemplate = ScribbleTemplateManager::getTemplateByName($innerTemplate);
		}
		catch(ScribbleTemplateException $e) {
			SystemLog::add($e->getmessage(), SystemLog::WARNING);
			return $this->editSelectTemplate($page);
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/edit.tpl.php');

		// Delete
		if(!empty($_POST) && isset($_POST['method']) && strtolower($_POST['method'])=='delete') {

			// Delete: Fail
			$pPermalink = $page->permalink;
			$pAbsUrl = $page->getAbsoluteUrl();
			if(!$page->getModelManager()->delete($page)) {
				$resultCode = 0;
			}

			// Delete: Success
			else {
				$user = ScribbleSession::getUser();
				LogManager::add("Page page:{$page->id}:{$pAbsUrl} was deleted by user:{$user->id}:{$user->displayname}", LogManager::USER_ACTION, $user->id);
				$resultCode = $page->id;
			}

			// Exit
			// If request was from AJAX, exit with a 0 (to indicate failure) or the
			// page's id (to indicate success).
			// If the request was NOT via AJAX then exit with an appropriate message
			if(isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
				die($resultCode);
			}
			else if($resultCode===0) {
				SystemLog::add('Failed to delete Page.', SystemLog::WARNING);
			}
			else {
				SystemLog::add('Page was removed successfully.', SystemLog::INFO);
				return new ScribbleAdminView();
			}
		}

		// Save
		else if(!empty($_POST)) {

			// Create a new version
			if($version->isInDatabase()) {
				$version = Model::create('PageVersion');
				$page->addRelatives($version, 'master');
			}

			// Convert dates
			if(isset($_POST['datepublish']) && isset($_POST['dateexpire']) && ScribbleAuth::authorize(ScribbleSession::getUser(), 'page-publish', $page->getPathToSite())) {
				list($d,$m,$y) = explode("/", $_POST['datepublish']);
				$_POST['datepublish'] = mktime(0, 0, 0, $m, $d, $y);
				list($d,$m,$y) = explode("/", $_POST['dateexpire']);
				$_POST['dateexpire'] = mktime(0, 0, 0, $m, $d, $y);
			}

			// Gather
			$preSaveIsPublished = $page->isPublished();
			$pageIsInDatabase = $page->isInDatabase();
			$page->page_id = $_POST['page_id'];
			$page->name = $_POST['name'];
			$page->title = $_POST['title'];
			$version->description = $_POST['description'];
			$page->isindex = $_POST['isindex'];
			$version->handlesubpaths = $_POST['handlesubpaths'];
			if(isset($_POST['datepublish']) && isset($_POST['dateexpire'])) {
				$page->datepublish = $_POST['ispublished']==1 ? $_POST['datepublish'] : 0;
				$page->dateexpire = $_POST['doexpire']==1 ? $_POST['dateexpire'] : 0;
			}
			$version->setOuterContent($outerTemplate->gatherPopulatorData($version, $_POST))->template = $outerTemplate->name;
			$version->setInnerContent($innerTemplate->gatherPopulatorData($version, $_POST))->template = $innerTemplate->name;
			$version->targetmenus = isset($_POST['targetmenus']) ? $_POST['targetmenus'] : array();
			$urlOption = isset($_POST['urloption']) ? $_POST['urloption'] : 'auto';
			if($urlOption=='relative') {
				$page->url = '';
				$page->customurl = PageManager::convertToUrl(preg_replace("/^\/+/", "", $_POST['customurl']['relative']));
			}
			elseif($urlOption=='absolute') {
				$page->url = '';
				$page->customurl = "/".PageManager::convertToUrl(preg_replace("/^\/*/", "", $_POST['customurl']['absolute']));
			}
			else{
				$page->customurl = '';
			}

			// Gather privileges
			$privileges = ScribbleAuthWidget::gatherPrivilegesFromPost($page->id, isset($_POST['privilege']) ? $_POST['privilege'] : array());

			// Gather tags
			$tags = preg_replace("/[^a-z0-9]/i", "", $_POST['tags'])=='' ? array() : explode(",", $_POST['tags']);
			$disownedTags = $version->setTagsArray($tags);

			// Set author of this new version
			$sUser = ScribbleSession::getUser();
			if(!$version->isInDatabase()) {
				$version->addRelatives($sUser, 'author');
			}

			// Set releaser (if only the releasetag is being modified)
			//if(!$preSaveIsPublished && $page->isPublished()) {
			//	$version->addRelatives($sUser, 'releaser');
			//}
			//$oc = $version->getOuterContent();
			//$oc->addRelatives($sUser);
			//$oc->authorname = $sUser->displayname;
			//$ic = $version->getInnerContent();
			//$ic->addRelatives($sUser);
			//$ic->authorname = $sUser->displayname;

			// Validate
			$errors = array();
			if(preg_replace("/[\s\t\r\n]/", "", $page->name)=='') {
				$errors[] = 'Invalid article name';
			}
			if(preg_replace("/[\s\t\r\n]/", "", $page->title)=='') {
				$errors[] = 'Invalid title';
			}
			$innerTemplate->validatePageContent($version, $errors);
			$outerTemplate->validatePageContent($version, $errors);
			if(!empty($errors)) {
				foreach($errors as $e) {
					SystemLog::add($e, SystemLog::WARNING);
				}
			}

			// Save: Fail
			else if(!$page->getModelManager()->save($page)) {
				SystemLog::add('Failed to save page.', SystemLog::WARNING);

				// Execute the templates' post-save method to give them chance to
				// manipulate data after this failed save
				$innerTemplate->saveHandler($page, FALSE);
				$outerTemplate->saveHandler($page, FALSE);

				// Trigger event
				$result = ScribbleEventManager::dispatchEvent(new ScribbleEvent('page-save-failure'), $page);
			}

			// Save: Success
			else {
				SystemLog::add('Page saved.', SystemLog::INFO);
	
				// Reset "isindex" flag on all other pages
				if($page->isindex==1) {
					$tp = Config::get('ext.Scribble.dbTablePrefix');
					ModelManager::sqlQuery("UPDATE {$tp}page SET isindex=0 WHERE id!=?", array($page->id));
				}

				// Remove any disowned tags
				foreach($disownedTags as $t) {
					$t->getModelManager()->delete($t);
				}

				// Save the privileges that were gathered from the POST data
				ScribbleAuthWidget::saveGatheredPrivileges($page->id, $privileges);

				// Execute the templates' post-save method to give them chance to
				// manipulate data after a successful save
				$innerTemplate->saveHandler($page, TRUE);
				$outerTemplate->saveHandler($page, TRUE);

				// Log
				$action = $pageIsInDatabase ? 'modified' : 'created';
				$user = ScribbleSession::getUser();
				LogManager::add("Page page:{$page->id}:{$page->getAbsoluteUrl()} was {$action} by user:{$user->id}:{$user->displayname}", LogManager::USER_ACTION, $user->id);

				// Trigger event
				$result = ScribbleEventManager::dispatchEvent(new ScribbleEvent('page-save-success'), $page);
			}
		}

		// Load parents and build into a tree heirarchy
		function buildParents(&$parents, $ignore='', $indent=0, $current=NULL) {
			$c = new ModelCriteria();
			$c->addClause(ModelCriteria::EQUALS, 'page_id', $current===NULL ? '' : $current->id);
			$c->addClause(ModelCriteria::EQUALS, 'site_id', SiteManager::getCurrentSite()->id);
			$c->addClause(ModelCriteria::NOT_EQUALS, 'id', $ignore===NULL ? '' : $ignore);
			$children = ModelManager::select('Page', $c);
			foreach($children as $parent) {
				$parent->_indent = $indent;
				$parents[] = $parent;
				buildParents($parents, $ignore, $indent+1, $parent);
			}
		}
		$parents = array();
		buildParents($parents, $page->id);
		$view->parents = $parents;

		// Get privileges data associated with the page
		$view->privilegeData = ScribbleAuthWidget::getResourcePrivileges($page->id, 'page-edit');

		// Load the latest outer/inner content model revisions and, for saved Pages,
		// the revision history
		$view->outerContent = $version->getOuterContent();
		$view->innerContent = $version->getInnerContent();
		//if($page->isInDatabase()) {
		//	$view->versionHistory = $page->getVersionHistory();
		//}

		// Result
		$view->page = $sView->page = $page;
		$view->version = $sView->version = $version;
		return $view;
	}

	/**
	* Select a template.
	*
	* @param PageVersionModel Page version for which a template is being selected
	* @return ScribbleAdminView
	*/
	private function editSelectTemplate($version) {

		// Load page
		$page = $version->findRelatives('Page', 'master')->get(0);

		// Change the View to present template choices
		$view = new ScribbleAdminView();
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/edit.select-template.tpl.php');
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/edit.select-template.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');
		$view->page = $page;

		// Dispatch the ui-inject events
		$sView->uiInjectionsWs = array();
		$sView->uiInjectionsLinks = array();
		ScribbleEventManager::dispatchEvent(new ScribbleEvent('ui-inject:/scribble/page/select-template/secondary-workspace'), $sView->uiInjectionsWs);
		ScribbleEventManager::dispatchEvent(new ScribbleEvent('ui-inject:/scribble/page/select-template/secondary-workspace/links'), $sView->uiInjectionsLinks);

		// Count how many times each template has been used, in the latest revision
		// of each Page, for information puposes
		$tp = Config::get('ext.Scribble.dbTablePrefix');
		$stmt = ModelManager::sqlQuery("SELECT COUNT(pc.template) AS pagecount, p.id, pc.template, pc.type FROM {$tp}page AS p
						LEFT JOIN {$tp}page_content AS pc ON p.id=pc.page_version_id
						GROUP BY p.id, pc.type, pc.template");
		while($row = $stmt->fetch(\PDO::FETCH_OBJ)) {
			$templatePageCount[$row->template] = $row->pagecount;
		}

		// Load all templates
		$templates = array(
			'outer'=>ScribbleTemplateManager::getTemplateChoices(ScribbleTemplate::TYPE_OUTER),
			'inner'=>ScribbleTemplateManager::getTemplateChoices(ScribbleTemplate::TYPE_INNER)
		);
		$initialSelection = array('outer'=>NULL, 'inner'=>NULL);
		foreach($templates as $io=>$ts) {
			foreach($ts as $k=>$t) {
				$tpl = new StdClass();
				$tpl->name = $t->name;
				$tpl->title = $t->getTitle();
				$tpl->pagecount = isset($templatePageCount[$t->name]) ? $templatePageCount[$t->name] : 0;
				$tpl->shortdescription = $t->getShortDescription();
				$templates[$io][$k] = $tpl;
				if($initialSelection[$io]===NULL || $tpl->pagecount>$initialSelection[$io]->pagecount) {
					$initialSelection[$io] = $tpl;
				}
			}
		}
		$view->templates = $templates;
		if(empty($templates)) {
			SystemLog::add("You haven't got any templates to add yet!", SystemLog::WARNING);
		}
		unset($templatePageCount);

		// Determine whether or not to show the "outer" list of templates.
		// If only one outer template is available, then we can auto-select it and
		// hide from the end user to make it more friendly.
		$view->showOuterListing = count($templates['outer'])>1;
		if(!$version->isInDatabase()) {
			$view->templateSelection = "{$initialSelection['outer']->name},{$initialSelection['inner']->name}";
		}
		else {
			$view->templateSelection = $version->getOuterContent()->template.','.$version->getInnerContent()->template;
		}

		// Return View
		return $view;
	}

	/**
	* List Pages.
	*
	* Accepts the following GET parameters:
	* string p	= Page ID. Load all subpages of this Page.
	*
	* @param array Action parameters
	* @return ScribbleAdminView
	*/
	public function index($params) {

		// Init
		$isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']);
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/index.tpl.php');

		// If there is no "current" site (e. it hasn't been setup or is misconfigured
		// in some way) then don't allow any further action.
		if(SiteManager::getCurrentSite()===NULL) {
			SystemLog::add('There is a problem with the current Site configuration which is preventing you from modifying any page content. Go to the Site admin UI and try and pick out the issue.', SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// Load all pages and count their children
		$tp = Config::get('ext.Scribble.dbTablePrefix');
		$c = new ModelCriteria();
		$c->addOrder('weight', 'ASC');
		$c->addClause(ModelCriteria::EQUALS, 'page_id', isset($_GET['p']) ? $_GET['p'] : '');
		$c->addClause(ModelCriteria::EQUALS, 'site_id', SiteManager::getCurrentSite()->id);
		$pages = ModelManager::select('Page', $c);
		foreach($pages as $k=>$p) {
			$p->_childcount = ModelManager::sqlQuery("SELECT COUNT(*) AS c FROM {$tp}page WHERE page_id=?", array($p->id))->fetch(PDO::FETCH_OBJ)->c;
			$p->_children = new ModelCollection();
			$p->_ispublished = $p->isPublished();
			$p->_hasexpired = $p->hasExpired();
		}

		// If a parent page has been specified ($_GET['p']), and the current request
		// has not originated from an AJAX call, then load each parent up to the
		// root node, including all of their children in order that the whole tree
		// can be rendered correctly.
		if(!$isAjax && isset($_GET['p']) && $_GET['p']!='') {
			$parentId = $_GET['p'];
			while($parentId!='') {
				$parent = Model::create('Page');
				$parent->id = $parentId;
				if($parent->getModelManager()->load($parent)) {
					$c = new ModelCriteria();
					$c->addClause(ModelCriteria::EQUALS, 'page_id', $parent->page_id);
					$c->addOrder('weight', 'ASC');
					$parentSiblings = ModelManager::select('Page', $c);
					foreach($parentSiblings as $ps) {
						if($ps->id===$parent->id) {
							$ps->_childcount = count($pages);
							$ps->_children = $pages;
							$ps->_ispublished = $ps->isPublished();
							$ps->_hasexpired = $ps->hasExpired();
						}
						else {
							$ps->_childcount = ModelManager::sqlQuery("SELECT COUNT(*) AS c FROM {$tp}page WHERE page_id=?", array($ps->id))->fetch(PDO::FETCH_OBJ)->c;
							$ps->_children = new ModelCollection();
							$ps->_ispublished = $ps->isPublished();
							$ps->_hasexpired = $ps->hasExpired();
						}
					}
					$pages = $parentSiblings;
					$parentId = $parent->page_id;
				}
				else {
					$parentId = '';
				}
			}
		}
		$view->pages = $pages;

		// If request originated via Ajax, then output Page list in JSON and exit
		if($isAjax) {
			$a = array();
			foreach($pages as $p) {
				unset($p->_children);
				$a[] = $p->getDbData();
			}
			die(json_encode($a));
		}

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/page/index.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Result
		return $view;
	}

	/**
	* [CALLED VIA AJAX ONLY] Apply the specified reordering of Pages.
	*
	* @param array Action parameters
	* @return View
	*/
	public function reorder($params) {

		// Apply weighting to all pages
		$order = $_POST['pageid'];
		$unorderedPages = ModelManager::select('Page');
		$pages = array();
		foreach($unorderedPages as $p) {
			$pages[$p->id] = $p;
		}
		foreach($order as $k=>$pid) {
			if(isset($pages[$pid]) && $k!=$pages[$pid]->weight) {
				$pages[$pid]->weight = $k;
				$pages[$pid]->getModelManager()->save($pages[$pid]);
			}
		}
		die("ok");
	}

	/*
	# @method View templateProxy( array $params )
	# $params	= Action parmameters (0=>template name)
	#
	# This method is a means to access methods in a template class via a url.
	# The URL should be in the format:
	#		/scribble/page/template-proxy/[template-name]/[hyphenated-class-method]
	#
	# The called class method must return a View instance.
	#
	# Example:
	#	- Your template "general" has a class method "getList".
	# - To access this method from javascript you call the URL:
	#		/scribble/page/template-proxy/general/get-list/x/y/z
	# - The extra parameters x, y, z are forwarded onto the class method
	*/
	public function templateProxy($params) {

		// Check parameters
		if(count($params)<2) {
			die("Invalid parameter count");
		}
		$templateName = array_shift($params);
		$templateMethod = Inflector::lowerHyphenated_lowerCamelCaps(array_shift($params));

		// Load template
		try {
			$template = ScribbleTemplateManager::getTemplateByName($templateName);
		}
		catch(Exception $e) {
			die($e->getMessage());
		}

		// Call method
		if(!method_exists($template, $templateMethod)) {
			die("Unknown method");
		}
		return $template->$templateMethod($params);
	}

	/**
	* Forwards through requests to the ::edit() method.
	*
	* @param array Action parameters
	* @param string Requested action. This is the Page ID.
	* @return View
	*/
	public function unknown($params, $action) {
		return $this->edit($params, $action);
	}
}
?>